package com.zlx.store.coupon.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import com.zlx.store.common.constant.RespCode;
import com.zlx.store.common.results.HttpResp;
import com.zlx.store.common.results.RespCode;
import com.zlx.store.coupon.constants.CouponConstant;
import com.zlx.store.coupon.constants.LogConstant;
import com.zlx.store.coupon.constants.RabbitMQConstants;
import com.zlx.store.coupon.dao.CouponDao;
import com.zlx.store.coupon.dao.CouponLogDao;
import com.zlx.store.coupon.dao.CouponRelateUserDao;
import com.zlx.store.domain.entity.CouponLog;
import com.zlx.store.domain.entity.LogMessage;
import com.zlx.store.coupon.job.BfCouponJob;
import com.zlx.store.coupon.service.CouponService;
import com.zlx.store.domain.entity.Coupon;
import com.zlx.store.domain.entity.CouponRelateUser;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
@Slf4j
@Service
public class CouponServiceImpl implements CouponService {


    @Autowired
    RedissonClient redissonClient;

    @Autowired
    CouponDao couponDao;

    @Autowired
    CouponRelateUserDao couponRelateUserDao;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    RedisTemplate couponRedisTemplate;

    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    CouponLogDao couponLogDao;


    /**
     * 往数据库添加优惠券
     *
     * @param coupon
     * @return
     */
    @Override
    public HttpResp putCoupon(Coupon coupon) {
        int insertResult = couponDao.insert(coupon);
        if (insertResult == 1) {
            return new HttpResp(RespCode.USER_OPERATION_SUCCESS, "优惠券添加成功", new Date());
        } else {
            return new HttpResp(RespCode.USER_OPERATION_ERROR, "优惠券添加失败", new Date());
        }
    }

    /**
     * 管理员删除数据库中的某优惠券,同时删除redis
     *
     * @param couponId
     * @return
     */
    @Override
    public HttpResp deleteCoupon(Long couponId) {
        /*如果紧急情况需要删除临时抢到的券,由于暂时只在redis中查询,所以需要同时删除redis的库存
        由于删除redis时,如果一个个删除中间表则会有很大的消耗,所以之间删除库存表,当要使用临时库存时,需要先查看库存表
        如果库存表存在临时优惠券,则可以使用,再删除用户所持有的数量否则,这个优惠券不能被使用
        */

        //如果库存表中也存在,则直接删除
        couponRedisTemplate.opsForHash().delete(CouponConstant.TEMP_COUPON_COUNT, couponId);

        //查询是否存在
        Coupon coupon = couponDao.selectById(couponId);

        if (coupon == null) {
            return new HttpResp(RespCode.USER_OPERATION_ERROR, "当前优惠券不存在", new Date());
        }

        if (couponDao.deleteById(couponId) == 1) {
            return new HttpResp(RespCode.USER_OPERATION_SUCCESS, "删除优惠券成功", new Date());
        }

        return new HttpResp(RespCode.USER_OPERATION_ERROR, "系统错误", new Date());
    }

    /**
     * 管理员修改优惠券属性,比如起止时间
     *
     * @param coupon
     * @return
     */
    @Override
    public HttpResp updateCoupon(Coupon coupon) {
        Coupon tempCoupon = couponDao.selectById(coupon.getCouponId());
        if (tempCoupon == null) {
            return new HttpResp(RespCode.USER_OPERATION_ERROR, "优惠券不存在,请重新选择", new Date());
        }

        if (couponDao.updateById(coupon) == 1) {
            //如果自动预热队列中存在,需要同步一下信息,如果已经被预热就不能修改,只能先删除再重新添加
            if (couponRedisTemplate.opsForHash().hasKey(CouponConstant.TEMP_COUPON_COUNT, coupon.getCouponId())) {
                couponRedisTemplate.opsForHash().put(CouponConstant.TEMP_COUPON_COUNT, coupon.getCouponId(), coupon);
            }

            return new HttpResp(RespCode.USER_OPERATION_SUCCESS, "修改优惠券成功", new Date());
        }
        return null;
    }

    /**
     * 返回所有的优惠券,分页
     *
     * @return
     */
    @Override
    public HttpResp showAllCoupon(int pageNum, int pageSize) {
        //提供分页所需要的信息
        Page<Coupon> Ipage = new Page<>(pageNum, pageSize);
        IPage<Coupon> page = couponDao.selectPage(Ipage, null);
        return new HttpResp(RespCode.USER_OPERATION_SUCCESS, page, new Date());
    }

    /**
     * 优惠券能否通过布隆过滤器
     *
     * @param couponId
     * @return
     */
    @Override
    public Long couponIsExistByBF(Long couponId) {
        return stringRedisTemplate.opsForValue().getOperations()
                .execute(new DefaultRedisScript<Long>(CouponConstant.luaS, Long.TYPE)
                        , Arrays.asList(CouponConstant.couponBF),
                        couponId.toString());
    }

    /**
     * 用户抢优惠券,保证被抢的券一定是经过预热的
     *
     * @param couponId
     * @param userId
     * @return
     */
    @Override
    public HttpResp snapUpCoupon(Long couponId, Long userId) {
        //判断布隆过滤器,防止恶意领券导致穿透
        if (couponIsExistByBF(couponId) == 0) {
            return new HttpResp(RespCode.USER_OPERATION_ERROR, "优惠券不存在", new Date());
        }

        //优惠券当前数量的锁
        RLock rLock = redissonClient.getLock(CouponConstant.PREPARING_COUPON_COUNT_LOCK);

        //当用户获取了某种优惠券,一般会在短期使用,这时不能直接加入到数据库中,避免不必要的损耗

        //1.查询优惠券是否已经在redis中
        Coupon tempCoupon = (Coupon) couponRedisTemplate.opsForHash().get(CouponConstant.PREPARING_COUPON_STOCK, couponId);


        //1.1.如果优惠券不在redis中,直接返回错误信息
        if (tempCoupon == null) {
            return new HttpResp(RespCode.USER_OPERATION_ERROR, "优惠券不存在", new Date());
        }

        //1.2.如果redis中有

        //判断抢券时间是否到了或者过期
        if (tempCoupon.getCouponStartTime().after(new Date())) {
            return new HttpResp(RespCode.USER_OPERATION_ERROR, "抢券时间还没到", new Date());
        }

        if (tempCoupon.getCouponEndTime().before(new Date())) {
            return new HttpResp(RespCode.USER_OPERATION_ERROR, "抢券时间已结束", new Date());
        }

        //如果时间条件满足,先判断防重队列的大小是否已经超过了库存量,如果超过了,直接返回错误信息
        Long queueSize = redisTemplate.opsForSet().size(CouponConstant.AVOID_DUPLICATION_QUEUE + couponId);
        if (queueSize != null && queueSize >= tempCoupon.getCouponStock()) {
            return new HttpResp(RespCode.USER_OPERATION_ERROR, "券已经被抢完", new Date());
        }

        //如果券没被领完则判断是否在队列中
        Boolean isMember = redisTemplate.opsForSet().isMember(CouponConstant.AVOID_DUPLICATION_QUEUE + couponId, userId);
        if (isMember) {
            //如果已经在队列中,则返回错误信息
            return new HttpResp(RespCode.USER_OPERATION_ERROR, "已经抢过,请勿重复操作", new Date());
        }
        //如果不在队列中,则加入队列中
        redisTemplate.opsForSet().add(CouponConstant.AVOID_DUPLICATION_QUEUE + couponId, String.valueOf(userId));

        //加锁判断中间表临时用户券是否存在以及当前数量是否大于零

        try {
            rLock.lock();
            Set<Long> tempSet = (HashSet) couponRedisTemplate.opsForHash().get(CouponConstant.TEMP_COUPON_COUNT, userId);
            if (tempSet != null && tempSet.contains(couponId)) {
                return new HttpResp(RespCode.USER_OPERATION_ERROR, "已经抢过,请勿重复操作", new Date());
            }

            //判断券的当前数量是否足够
            Integer count = (Integer) couponRedisTemplate.opsForHash().get(CouponConstant.PREPARING_COUPON_COUNT, couponId);
            if (count != null && count <= 0) {
                return new HttpResp(RespCode.USER_OPERATION_ERROR, "券已经被抢完", new Date());
            }
            couponRedisTemplate.opsForHash().put(CouponConstant.PREPARING_COUPON_COUNT, couponId, count - 1);

            //将券加入到用户临时优惠券中

            //防止tempSet为空,这里需要新建一个set
            if (tempSet == null) {
                couponRedisTemplate.opsForHash().put(CouponConstant.TEMP_COUPON_COUNT, userId, new HashSet<>(Arrays.asList(couponId)));
            } else {
                tempSet.add(couponId);
                couponRedisTemplate.opsForHash().put(CouponConstant.TEMP_COUPON_COUNT, userId, tempSet);
            }
            return new HttpResp(RespCode.USER_OPERATION_SUCCESS, "恭喜抢到券了", new Date());
        } catch (Exception e) {
            return new HttpResp(RespCode.USER_OPERATION_ERROR, "系统错误", new Date());
        } finally {
            rLock.unlock();

        }
    }

    /**
     * 手动预热优惠券
     *
     * @param couponId
     * @return
     */
    @Override
    public HttpResp prepareCoupon(Long couponId) {
        // 检查当前券是否已经预热过了
        Boolean hasKey = couponRedisTemplate.opsForHash().hasKey(CouponConstant.PREPARING_COUPON_STOCK, couponId);
        if (hasKey) {
            // 如果已经预热,直接返回错误信息
            return new HttpResp(RespCode.USER_OPERATION_ERROR, "当前优惠券已经被预热", new Date());
        }

        // 判断当前coupon是否在数据库中
        Coupon coupon = couponDao.selectById(couponId);

        if (coupon == null) {
            // 如果查询的数据不存在
            return new HttpResp(RespCode.USER_OPERATION_ERROR, "被预热的优惠券不存在", new Date());
        }

        // 如果条件都满足,则将当前优惠券和其库存加入redis中
        couponRedisTemplate.opsForHash().put(CouponConstant.PREPARING_COUPON_STOCK, couponId, coupon);

        //由于临时表只需要查询优惠券数量,所以只存入数量即可
        couponRedisTemplate.opsForHash().put(CouponConstant.PREPARING_COUPON_COUNT, couponId, coupon.getCouponStock());

        return new HttpResp(RespCode.USER_OPERATION_SUCCESS, "预热成功", new Date());
    }

    /**
     * 将要预热的优惠券加入hash
     *
     * @return
     */
    @Override
    public HttpResp addToPreQueue(Long couponId) {
        //检查优惠券是否存在
        //判断布隆过滤器
        if (couponIsExistByBF(couponId) == 0) {
            return new HttpResp(RespCode.USER_OPERATION_ERROR, "优惠券不存在", new Date());
        }
        //存在则去数据库获取
        Coupon coupon = couponDao.selectById(couponId);

        if (coupon == null) {
            return new HttpResp(RespCode.USER_OPERATION_ERROR, "优惠券不存在", new Date());
        }

        couponRedisTemplate.opsForHash().put(CouponConstant.AUTO_PREPARE_QUEUE, couponId, coupon);
        return new HttpResp(RespCode.USER_OPERATION_SUCCESS, "添加至自动预热队列成功", new Date());
    }

    /**
     * 从自动预热队列中移除
     * @param couponId
     * @return
     */
    @Override
    public HttpResp removeFromPreQueue(Long couponId) {
        //判断是否在set中
        Long remove = couponRedisTemplate.opsForHash().delete(CouponConstant.AUTO_PREPARE_QUEUE, couponId);
        if (remove == 1) {
            return new HttpResp(RespCode.USER_OPERATION_SUCCESS, "优惠券移除成功", new Date());
        }
        return new HttpResp(RespCode.USER_OPERATION_ERROR, "优惠券不在队列中", new Date());
    }

    /**
     * 展示自动预热列表中的内容,未被预热的优惠券
     *
     * @return
     */
    @Override
    public HttpResp showPreQueue() {
        Map<Object, Object> map = couponRedisTemplate.opsForHash().entries(CouponConstant.AUTO_PREPARE_QUEUE);
        return new HttpResp(RespCode.USER_OPERATION_SUCCESS, map.values(), new Date());
    }

    /**
     * 展示已经被预热的优惠券总库存
     *
     * @return
     */
    @Override
    public HttpResp showCouponStock() {
        Map<Object, Object> map = couponRedisTemplate.opsForHash().entries(CouponConstant.PREPARING_COUPON_STOCK);
        return new HttpResp(RespCode.USER_OPERATION_SUCCESS, map.values(), new Date());
    }

    /**
     * 展示当前剩余多少券
     *
     * @return
     */
    @Override
    public HttpResp showCouponCount() {
        Map entries = couponRedisTemplate.opsForHash().entries(CouponConstant.PREPARING_COUPON_COUNT);
        return new HttpResp(RespCode.USER_OPERATION_SUCCESS, entries, new Date());
    }

    /**
     * 展示用户拥有的优惠券
     *
     * @return
     */
    @Override
    public HttpResp showCouponByUser(Long userId) {
        //创建一个存储优惠券的list
        List<Coupon> couponList = new ArrayList<>();
        //先查询redis的中间表
        Set<Long> set = (HashSet<Long>) couponRedisTemplate.opsForHash().get(CouponConstant.TEMP_COUPON_COUNT, userId);
        if (set != null) {
            set.forEach(
                    c -> {
                        //去总库存表获得优惠券对象
                        Coupon o = (Coupon) couponRedisTemplate.opsForHash().get(CouponConstant.PREPARING_COUPON_STOCK, c);
                        //将对象中的库存置空,防止数据泄露
                        if (o != null) {
                            o.setCouponStock(null);
                            o.setCouponCreateBy(null);
                            o.setCouponCreateTime(null);
                        }
                        couponList.add(o);
                    });
        }

        //再去数据库查询
        LambdaQueryWrapper<CouponRelateUser> wrapper = new LambdaQueryWrapper();
        wrapper.eq(CouponRelateUser::getUserId, userId);
        List<CouponRelateUser> couponRelateUsers = couponRelateUserDao.selectList(wrapper);

        //遍历中间类,查询优惠券并记录,注意隐藏部分信息
        LambdaQueryWrapper<Coupon> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper
                .select(Coupon::getCouponId)
                .select(Coupon::getCouponName)
                .select(Coupon::getCouponCategory)
                .select(Coupon::getCouponDiscount)
                .select(Coupon::getCouponTargetMoney)
                .select(Coupon::getCouponReductionMoney)
                .select(Coupon::getCouponStartTime)
                .select(Coupon::getCouponEndTime);

        for (CouponRelateUser couponRelateUser : couponRelateUsers) {
            queryWrapper.eq(Coupon::getCouponId, couponRelateUser.getCouponId());
            couponList.add(couponDao.selectOne(queryWrapper));
        }

        return new HttpResp(RespCode.USER_OPERATION_SUCCESS, couponList, new Date());
    }

    /**
     * 用户使用某张优惠券,暂且规定一次购物只能使用一张优惠券,只在支付时调用
     *
     * @param couponId
     * @param userId
     * @return
     */
    @Override
    public HttpResp useCoupon(Long couponId, Long userId, Double total) {
        //判断优惠券是否存在
        if (couponIsExistByBF(couponId) != 1) {
            return new HttpResp(RespCode.USER_OPERATION_ERROR, "优惠券不存在", new Date());
        }
        Coupon tempCoupon = (Coupon) couponRedisTemplate.opsForHash().get(CouponConstant.PREPARING_COUPON_STOCK, couponId);
        //查询redis库存表,如果存在则直接取出,优惠券一旦使用后不能因为取消订单而退回
        if (tempCoupon != null) {
            if (tempCoupon.getCouponStartTime().after(new Date())) {
                return new HttpResp(RespCode.USER_OPERATION_ERROR, "优惠券未到使用时间", new Date());
            }
            if (tempCoupon.getCouponEndTime().before(new Date())) {
                return new HttpResp(RespCode.USER_OPERATION_ERROR, "优惠券已经过期", new Date());
            }

            if (tempCoupon.getCouponTargetMoney() != null && tempCoupon.getCouponTargetMoney() > total) {
                return new HttpResp(RespCode.USER_OPERATION_ERROR, "未达到优惠券的使用金额", new Date());
            }
            //判断成功后加锁
            RLock lock = redissonClient.getLock(CouponConstant.TEMP_COUPON_COUNT_LOCK);
            try {
                lock.lock();
                HashSet<Long> set = (HashSet) couponRedisTemplate.opsForHash().get(CouponConstant.TEMP_COUPON_COUNT, userId);
                if (set != null && set.contains(couponId)) {
                    //将券的数量减少
                    set.remove(couponId);
                    couponRedisTemplate.opsForHash().put(CouponConstant.TEMP_COUPON_COUNT, userId, set);
                    //将优惠券的信息传回去,而且这个请求只能接受其他服务器的调度,不能接受用户的直接调用
                    return new HttpResp(RespCode.USER_OPERATION_SUCCESS, tempCoupon, new Date());
                }
            } catch (Exception e) {
                return new HttpResp(RespCode.USER_OPERATION_ERROR, "系统错误", new Date());
            } finally {
                lock.unlock();
            }
        }

        //不在redis中则直接查询数据库
        LambdaQueryWrapper<CouponRelateUser> wrapper = new LambdaQueryWrapper();
        wrapper.eq(CouponRelateUser::getCouponId, couponId)
                .eq(CouponRelateUser::getUserId, userId);
        CouponRelateUser couponRelateUser = couponRelateUserDao.selectOne(wrapper);
        if (couponRelateUser == null) {
            return new HttpResp(RespCode.USER_OPERATION_ERROR, "优惠券不存在", new Date());
        }
        Coupon coupon = couponDao.selectById(couponRelateUser.getCouponId());
        if (coupon.getCouponStartTime().after(new Date())) {
            return new HttpResp(RespCode.USER_OPERATION_ERROR, "优惠券未到使用时间", new Date());
        }
        if (coupon.getCouponEndTime().before(new Date())) {
            return new HttpResp(RespCode.USER_OPERATION_ERROR, "优惠券已经过期", new Date());
        }

        if (coupon.getCouponTargetMoney() != null && coupon.getCouponTargetMoney() > total) {
            return new HttpResp(RespCode.USER_OPERATION_ERROR, "未达到优惠券的使用金额", new Date());
        }
        couponRelateUserDao.deleteById(couponRelateUser.getCouponId());
        return new HttpResp(RespCode.USER_OPERATION_SUCCESS, coupon, new Date());

    }


    /**
     * 取消订单时回退优惠券,如果优惠券过期不用回退
     *
     * @param couponId
     * @param userId
     * @return
     */
    @Override
    public HttpResp rollBackCoupon(Long couponId, Long userId) {
        //如果优惠券在redis中就返回redis

        //加锁,防止数据库更新时出现并发错误
        RLock lock = redissonClient.getLock(CouponConstant.PREPARING_COUPON_COUNT_LOCK);
        try {
            lock.lock();
            Coupon tempCoupon = (Coupon) redisTemplate.opsForHash().get(CouponConstant.PREPARING_COUPON_STOCK, couponId);
            if (tempCoupon != null) {
                Set<Long> set = (HashSet) couponRedisTemplate.opsForHash().get(CouponConstant.TEMP_COUPON_COUNT, userId);
                set.add(couponId);
                couponRedisTemplate.opsForHash().put(CouponConstant.PREPARING_COUPON_STOCK, userId, set);
                return new HttpResp(RespCode.USER_OPERATION_SUCCESS, "回滚购物券成功", new Date());

            }
        } catch (Exception e) {
            return new HttpResp(RespCode.USER_OPERATION_ERROR, "系统错误", new Date());
        } finally {
            lock.unlock();
        }
        //如果redis不存在则直接数据库更新
        Coupon coupon = couponDao.selectById(couponId);
        if (coupon == null) {
            return new HttpResp(RespCode.USER_OPERATION_ERROR, "优惠券不存在", new Date());
        }
        CouponRelateUser couponRelateUser = new CouponRelateUser();
        couponRelateUser.setCouponId(couponId);
        couponRelateUser.setUserId(userId);
        couponRelateUserDao.insert(couponRelateUser);
        return new HttpResp(RespCode.USER_OPERATION_SUCCESS, "回滚优惠券成功", new Date());
    }

    /**
     * 给新注册的用户发一张新人券
     * @param userId
     * @return
     */
    @Override
    public HttpResp getCouponForNew(Long userId) {
        //检查是否领过
        LambdaQueryWrapper<CouponRelateUser> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CouponRelateUser::getUserId, userId)
                .eq(CouponRelateUser::getCouponId, CouponConstant.forNewComerCouponId);
        if (couponRelateUserDao.selectOne(wrapper) != null) {
            return new HttpResp(RespCode.USER_OPERATION_ERROR, "已经领取过新人券", new Date());
        }
        CouponRelateUser couponRelateUser = new CouponRelateUser();
        couponRelateUser.setCouponId(CouponConstant.forNewComerCouponId);
        couponRelateUser.setUserId(userId);
        couponRelateUserDao.insert(couponRelateUser);
        return new HttpResp(RespCode.USER_OPERATION_SUCCESS, "领取优惠券成功", new Date());
    }

    /**
     * 监听延时队列,将消息存入mysql中
     * @param logMessage
     */
    @RabbitListener(queues = RabbitMQConstants.DELAY_QUEUE)
    @Override
    public void writeLogToMysql(LogMessage logMessage) {
        log.debug("拿到了消息" + new Date());

        CouponLog couponLog = new CouponLog();
        couponLog.setLogLevel(LogConstant.INFO);
        couponLog.setCreateTime(logMessage.getHttpResp().getTime());
        //到redis中查询优惠券的详细信息加入到message中
        StringBuilder message = new StringBuilder("用户id:" + logMessage.getUserId() + "\n"
                +"优惠券id:"+logMessage.getCouponId()+"\n"
                +"执行信息:");
        if (logMessage.getHttpResp() != null) {
            try {
                message = message.append(new ObjectMapper().writeValueAsString(logMessage.getHttpResp()));
                Coupon coupon = (Coupon)couponRedisTemplate.opsForHash().get(CouponConstant.PREPARING_COUPON_STOCK, Long.valueOf(logMessage.getCouponId()));
                if (coupon != null) {
                    message = message.append("\n优惠券详情:"+new ObjectMapper().writeValueAsString(coupon));
                }
            } catch (JsonProcessingException e) {
                log.error("抢券日志生成错误,json的转换出现问题");
                throw new RuntimeException(e);
            }
        }

        couponLog.setLogMessage(message.toString());
        if (couponLogDao.insert(couponLog) != 1) {
            log.error("抢券日志写入数据库错误");
        }
    }
}
